package org.dromara.common.web.handler;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpStatus;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.exception.base.BaseException;
import org.dromara.common.core.utils.StreamUtils;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;


/**
 * @Author 刘武贵
 * @Date 2024/8/15 22:00
 * @Description 功能描述：全局异常处理器
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 请求方式不支持
     * 处理不支持的HTTP请求方法的异常
     *
     * 当应用程序接收到一个其配置中未定义的HTTP请求方法时，将调用此方法来处理异常
     * 该方法的主要目的是记录错误的请求URI和不被支持的请求方法，并返回一个表示请求方法不合法的响应
     *
     * @param e HttpRequestMethodNotSupportedException 异常对象，包含不被支持的请求方法和相关错误信息
     * @param request HttpServletRequest 请求对象，用于获取请求的URI和其他信息
     * @return R<Void> 返回一个泛型为Void的R对象，表示处理结果，其中包含错误状态码和错误信息
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public R<Void> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
                                                                HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
        return R.fail(HttpStatus.HTTP_BAD_METHOD, e.getMessage());
    }

    /**
     * 业务异常
     * 处理服务异常
     * 此方法用于处理和响应服务异常，记录异常信息并返回相应的错误代码或默认错误信息
     *
     * @param e ServiceException异常对象，包含错误信息和可能的错误代码
     * @param request HttpServletRequest对象，表示客户端请求，本方法中未使用
     * @return 返回一个R对象，其中包含处理结果 Void类型表示没有返回值
     *
     * 解释：
     * - log.error(e.getMessage())：记录异常信息，使用日志记录器记录异常消息，方便问题追踪和定位
     * - Integer code = e.getCode()：获取异常代码，从ServiceException异常对象中提取错误代码
     * - ObjectUtil.isNotNull(code)：检查错误代码是否为null，用于判断是否有特定的错误代码
     * - R.fail(code, e.getMessage())：创建失败响应，携带错误代码和错误消息
     * - R.fail(e.getMessage())：如果无错误代码，仅携带错误消息创建失败响应
     *
     * 为什么这么做：
     * 为了能够统一管理和响应异常，此方法允许系统以标准化的方式处理和报告错误，便于前端展示和用户理解
     */
    @ExceptionHandler(ServiceException.class)
    public R<Void> handleServiceException(ServiceException e, HttpServletRequest request) {
        log.error(e.getMessage());
        Integer code = e.getCode();
        return ObjectUtil.isNotNull(code) ? R.fail(code, e.getMessage()) : R.fail(e.getMessage());
    }

    /**
     * 业务异常
     * 处理自定义基础异常的处理器
     *
     * 本函数负责捕获并处理自定义的BaseException，记录错误信息并返回一个标准化的错误响应
     * 主要用于统一管理API异常，确保所有的异常反馈都遵循相同的格式，提高错误处理的一致性和可读性
     *
     * @param e 抛出的BaseException对象，包含了错误的详细信息
     * @param request 当前HTTP请求对象，用于获取请求相关的上下文信息
     * @return 返回一个R对象，其中包含错误信息本次请求结果为失败
     */
    @ExceptionHandler(BaseException.class)
    public R<Void> handleBaseException(BaseException e, HttpServletRequest request) {
        log.error(e.getMessage());
        return R.fail(e.getMessage());
    }

    /**
     * 请求路径中缺少必需的路径变量
     * 处理缺少路径变量的异常
     * 当请求路径中缺少必需的路径变量时，该方法会被调用，记录错误日志并返回一个失败的结果
     *
     * @param e 异常对象，包含异常相关信息
     * @param request HTTP请求对象，用于获取请求的URI
     * @return 返回一个R类型的对象，其中包含操作结果，如果缺少必需的路径变量，则操作失败
     */
    @ExceptionHandler(MissingPathVariableException.class)
    public R<Void> handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI);
        return R.fail(String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName()));
    }

    /**
     * 请求参数类型不匹配
     * 处理请求参数类型不匹配异常
     * 当请求中的参数类型与方法中定义的参数类型不一致时，会抛出此异常
     * 此方法记录异常信息并返回错误响应
     *
     * @param e            MethodArgumentTypeMismatchException异常对象，包含参数类型不匹配的信息
     * @param request      HTTP请求对象，用于获取请求的URI
     * @return 返回一个泛型为Void的R对象，表示处理结果，结果中包含错误信息
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public R<Void> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求参数类型不匹配'{}',发生系统异常.", requestURI);
        return R.fail(String.format("请求参数类型不匹配，参数[%s]要求类型为：'%s'，但输入值为：'%s'", e.getName(), e.getRequiredType().getName(), e.getValue()));
    }

    /**
     * 找不到路由
     * 处理未找到处理器异常
     * 当请求的URL在应用中没有对应的处理器时，会抛出NoHandlerFoundException
     * 此方法用于捕获该异常，并返回适当的响应
     *
     * @param e           NoHandlerFoundException异常对象，包含异常信息和堆栈跟踪
     * @param request     HttpServletRequest对象，用于获取请求的相关信息，如请求URI
     * @return 返回一个R类型的响应对象，其中包含Void类型的数据部分
     *         表示请求的处理器未找到，并附带相应的HTTP状态码和错误信息
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    public R<Void> handleNoHandlerFoundException(NoHandlerFoundException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}'不存在.", requestURI);
        return R.fail(HttpStatus.HTTP_NOT_FOUND, e.getMessage());
    }

    /**
     * 拦截未知的运行时异常
     * 处理运行时异常的方法
     *
     * 当系统中发生未被捕获的运行时异常时，此方法将被调用它主要用于日志记录异常信息，
     * 包括发生异常的请求URI和异常详情，然后返回一个表示操作失败的结果对象，其中包含异常的详细信息
     *
     * @param e 运行时异常对象，代表发生的未知异常
     * @param request HttpServletRequest对象，用于获取发生异常的请求的URI
     * @return 返回一个R类型的对象，泛型为Void，表示操作结果携带的信息是失败原因
     */
    @ExceptionHandler(RuntimeException.class)
    public R<Void> handleRuntimeException(RuntimeException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',发生未知异常.", requestURI, e);
        return R.fail(e.getMessage());
    }

    /**
     * 系统异常
     * 处理异常并返回响应结果
     *
     * @param e 异常对象，用于处理捕获到的异常
     * @param request HttpServletRequest对象，用于获取请求相关信息
     * @return 返回一个泛型为Void的R对象，用于向调用方说明处理结果
     *
     * 此方法主要用于处理并记录请求过程中发生的异常情况，通过日志记录异常信息和请求URI，
     * 并基于异常信息构建并返回一个失败的响应对象
     */
    @ExceptionHandler(Exception.class)
    public R<Void> handleException(Exception e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',发生系统异常.", requestURI, e);
        return R.fail(e.getMessage());
    }

    /**
     * 自定义验证异常
     * 处理绑定异常
     *
     * 当Web层发生数据绑定异常时调用此方法进行处理
     *
     * @param e 绑定异常对象，包含所有绑定错误信息
     * @return 返回一个R对象，其中包含Void类型数据，用于表示操作结果
     *
     * 此方法首先记录异常信息，然后将所有错误消息合并为一个字符串，并通过失败的结果对象返回
     * 这种处理方式有助于前端快速获取错误详情，便于用户界面友好地展示错误信息
     */
    @ExceptionHandler(BindException.class)
    public R<Void> handleBindException(BindException e) {
        log.error(e.getMessage());
        String message = StreamUtils.join(e.getAllErrors(), DefaultMessageSourceResolvable::getDefaultMessage, ", ");
        return R.fail(message);
    }

    /**
     * 自定义验证异常
     * 处理约束违规异常
     * 当API抛出ConstraintViolationException异常时，此方法将被捕获并处理该异常
     * 它记录错误信息并收集所有约束违规的消息，然后以失败的状态和收集的消息返回响应
     *
     * @param e ConstraintViolationException异常对象，包含所有约束违规的信息
     * @return 返回一个R对象，其中包含Void类型的结果和收集的错误消息在失败的情况下
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public R<Void> constraintViolationException(ConstraintViolationException e) {
        log.error(e.getMessage());
        String message = StreamUtils.join(e.getConstraintViolations(), ConstraintViolation::getMessage, ", ");
        return R.fail(message);
    }

    /**
     * 自定义验证异常
     * 处理方法参数无效异常
     * 当控制器方法的参数无法通过验证时，会抛出MethodArgumentNotValidException
     * 此处理方法用于捕获该异常，并返回相应的错误信息
     *
     * @param e MethodArgumentNotValidException异常对象
     * @return 返回一个R对象，表示请求的结果
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public R<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        // 记录异常信息
        log.error(e.getMessage());
        // 获取字段验证错误的默认消息
        String message = e.getBindingResult().getFieldError().getDefaultMessage();
        // 返回验证失败的结果，携带错误消息
        return R.fail(message);
    }

}
